/*
 *  tracker/TrackerShortCuts.cpp
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 *  TrackerShortCuts.cpp
 *  MilkyTracker
 *
 *  Created by Peter Barth on Thu May 19 2005.
 *
 */

#include "Tracker.h"
#include "ControlIDs.h"
#include "Screen.h"
#include "Event.h"
#include "PlayerController.h"
#include "PlayerLogic.h"
#include "RecorderLogic.h"

#include "Container.h"
#include "ListBox.h"
#include "PatternEditorControl.h"

#include "ModuleEditor.h"
#include "TrackerConfig.h"

#include "InputControlListener.h"
#include "SectionInstruments.h"
#include "SectionTranspose.h"
#include "SectionDiskMenu.h"

void Tracker::sendNoteDown(mp_sint32 note, pp_int32 volume /* = -1*/)
{
    if (volume != -1 && volume > 255)
        volume = 255;

    // Volume here is between 0 to 255, but don't forget to make the volume FT2 compatible (0..64)
    inputControlListener->sendNote(note | InputControlListener::KEY_PRESS,
                                   volume == -1 ? -1 : (signed)PatternTools::normalizeVol(volume));
}

void Tracker::sendNoteUp(mp_sint32 note)
{
    // bit 16 indicates key release
    inputControlListener->sendNote(note | InputControlListener::KEY_RELEASE);
}

void Tracker::processShortcuts(PPEvent* event)
{
    if (processMessageBoxShortcuts(event))
        return;

    switch (editMode)
    {
        case EditModeMilkyTracker:
            processShortcutsMilkyTracker(event);
            break;

        case EditModeFastTracker:
            processShortcutsFastTracker(event);
            break;

        default:
            ASSERT(false);
    }
}

void Tracker::processShortcutsMilkyTracker(PPEvent* event)
{
    if (event->getID() == eKeyDown)
    {
        pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());
        pp_uint16 scanCode = *(((pp_uint16*)event->getDataPtr()) + 1);
        switch (keyCode)
        {
            case VK_F1:
            case VK_F2:
            case VK_F3:
            case VK_F4:
            case VK_F5:
            case VK_F6:
            case VK_F7:
            case VK_F8:
            case VK_F9:
            case VK_F10:
            case VK_F11:
            case VK_F12:
            {
                if (::getKeyModifier())
                    goto processBindings;

                if (static_cast<PPControl*>(getPatternEditorControl()) != screen->getFocusedControl())
                {
                    getPatternEditorControl()->dispatchEvent(event);
                }
                break;
            }

            default:
            {
processBindings:
                pp_int32 keyModifier = ::getKeyModifier();
                bool res = executeBinding(eventKeyDownBindings, keyCode);

                if (res && !isActiveEditing())
                    event->cancel();

                if (res || keyModifier)
                    break;

                if (editMode == EditModeMilkyTracker)
                {
                    if (sectionDiskMenu->isFileBrowserVisible() &&
                        sectionDiskMenu->fileBrowserHasFocus())
                        break;
                }

                PatternEditorControl* patternEditorControl = getPatternEditorControl();

                // translate key to note
                pp_int32 note = patternEditorControl->ScanCodeToNote(scanCode);

                recorderLogic->sendNoteDownToPatternEditor(event, note, patternEditorControl);
                break;
            }

        }
    }
    else if (event->getID() == eKeyUp)
    {
        pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());
        pp_uint16 scanCode = *(((pp_uint16*)event->getDataPtr()) + 1);

        switch (keyCode)
        {
            case VK_SPACE:
            {
                playerLogic->finishTraceAndRowPlay();
                break;
            }

            default:
            {
                PatternEditorControl* patternEditorControl = getPatternEditorControl();

                pp_int32 note = patternEditorControl->ScanCodeToNote(scanCode);

                recorderLogic->sendNoteUpToPatternEditor(event, note, patternEditorControl);
            }
        }

    }
}

void Tracker::selectNextOrder(bool wrap /* = false*/)
{
    if (wrap && listBoxOrderList->isLastEntry())
    {
        setOrderListIndex(0);
        return;
    }

    pp_uint16 vk[3] = { VK_DOWN, 0, 0 };
    PPEvent e(eKeyDown, &vk, sizeof(vk));
    listBoxOrderList->dispatchEvent(&e);
}

void Tracker::selectPreviousOrder(bool wrap /* = false*/)
{
    if (wrap && listBoxOrderList->isFirstEntry())
    {
        setOrderListIndex(listBoxOrderList->getNumItems() - 1);
        return;
    }

    pp_uint16 vk[3] = { VK_UP, 0, 0 };
    PPEvent e(eKeyDown, &vk, sizeof(vk));
    listBoxOrderList->dispatchEvent(&e);
}

void Tracker::selectNextInstrument()
{
    pp_uint16 vk[3] = { VK_DOWN, 0, 0 };
    PPEvent e(eKeyDown, &vk, sizeof(vk));
    listBoxInstruments->dispatchEvent(&e);
}

void Tracker::selectPreviousInstrument()
{
    pp_uint16 vk[3] = { VK_UP, 0, 0 };
    PPEvent e(eKeyDown, &vk, sizeof(vk));
    listBoxInstruments->dispatchEvent(&e);
}

///////////////////////////////////////////////////////////////////////////////
// The Fasttracker II compatibility mode is really just a big hack, because
// MilkyTracker uses focus handling on most editable controls while FT2 doesn't
// ----------------------------------------------------------------------------
// 1. a defined set of keys always are always routed to to the pattern editor
// 2. If record mode is ON all keyboard events are also routed to pattern editor
//    (no matter if it can handle them or not)
// 3. Keys are not routed into any other control except for editing
///////////////////////////////////////////////////////////////////////////////
void Tracker::processShortcutsFastTracker(PPEvent* event)
{
    if (isActiveEditing())
        return;

    /*if (screen->getFocusedControl() != static_cast<PPControl*>(getPatternEditorControl()))
       {
        screen->setFocus(getPatternEditorControl());
        screen->paintControl(getPatternEditorControl());
       }*/

    if (event->getID() == eKeyDown)
    {
        pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());
        pp_uint16 scanCode = *(((pp_uint16*)event->getDataPtr()) + 1);

        switch (scanCode)
        {
            case SC_WTF:
                if (!::getKeyModifier() || ::getKeyModifier() == KeyModifierSHIFT)
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                    keyCode = 0;
                }
                break;

            // Place cursor in channel
            case SC_Q:
            case SC_W:
            case SC_E:
            case SC_R:
            case SC_T:
            case SC_Z:
            case SC_U:
            case SC_I:
            case SC_A:
            case SC_S:
            case SC_D:
            case SC_F:
            case SC_G:
            case SC_H:
            case SC_J:
            case SC_K:
                if (screen->getModalControl())
                    break;

                if (::getKeyModifier() == KeyModifierALT)
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                    keyCode = 0;
                }
                break;
        }

        switch (keyCode)
        {
            case VK_SPACE:
            {
                if (screen->getModalControl())
                    break;

                if (::getKeyModifier())
                    goto processOthers;

                if (playerController->isPlaying() || playerController->isPlayingPattern())
                {
                    playerLogic->stopSong();
                    event->cancel();
                    break;
                }

                playerLogic->stopSong();

                eventKeyDownBinding_ToggleFT2Edit();

                event->cancel();
                break;
            }

            // Those are the key combinations which are always routed to pattern editor control as long
            // as we're in Fasttracker editing mode
            case VK_ALT:
            case VK_SHIFT:
            case VK_CONTROL:
                if (screen->getModalControl())
                    break;

                getPatternEditorControl()->dispatchEvent(event);
                event->cancel();
                break;

            // Transpose (regardless of modifers)
            case VK_F1:
            case VK_F2:
            case VK_F7:
            case VK_F8:
            case VK_F9:
            case VK_F10:
            case VK_F11:
            case VK_F12:
                processShortcutsMilkyTracker(event);
                break;

            // Cut copy paste
            case VK_F3:
            case VK_F4:
            case VK_F5:
            case VK_F6:
                // Global meaning here
                if (::getKeyModifier())
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                    break;
                }
                processShortcutsMilkyTracker(event);
                break;

            // Some special keys always going to the pattern editor (like undo, redo, mute etc.)
            case 'A':
            case 'C':
            case 'V':
            case 'X':
            case 'Z':
            case 'Y':
                if (screen->getModalControl())
                {
                    // those seem to be piano keys, they're used in some
                    // modal dialogs for instrument preview playback
                    if (!::getKeyModifier())
                        goto processOthers;

                    break;
                }

                if (::getKeyModifier() == (KeyModifierCTRL | KeyModifierALT))
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                }
                else goto processOthers;
                break;

            case 'I':
                if (screen->getModalControl())
                    break;

                if (::getKeyModifier() == KeyModifierSHIFT)
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                }
                else goto processOthers;
                break;

            case 'M':
                if (screen->getModalControl())
                    break;

                if (::getKeyModifier() == KeyModifierSHIFT ||
                    ::getKeyModifier() == (KeyModifierSHIFT | KeyModifierCTRL))
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                }
                else goto processOthers;
                break;

            case VK_UP:
            case VK_DOWN:
            case VK_LEFT:
            case VK_RIGHT:
            case VK_HOME:
            case VK_END:
            case VK_PRIOR:
            case VK_NEXT:
                if (screen->getModalControl())
                    break;

                if (!::getKeyModifier() ||
                    ::getKeyModifier() == KeyModifierALT ||
                    ::getKeyModifier() == (KeyModifierSHIFT | KeyModifierALT))
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                }
                else if (::getKeyModifier() == KeyModifierSHIFT)
                {
                    switch (keyCode)
                    {
                        // Select instrument using Shift+Up/Down
                        case VK_UP:
                        case VK_DOWN:
                        case VK_NEXT:
                        case VK_PRIOR:
                            listBoxInstruments->dispatchEvent(event);
                            event->cancel();
                            break;

                        // Select new order using Shift+Left/Right
                        case VK_LEFT:
                        {
                            selectPreviousOrder();
                            event->cancel();
                            break;
                        }
                        case VK_RIGHT:
                        {
                            selectNextOrder();
                            event->cancel();
                            break;
                        }
                    }
                }
                else if (::getKeyModifier() == (KeyModifierSHIFT | KeyModifierCTRL))
                {
                    switch (keyCode)
                    {
                        // Select sample using Shift+Alt+Up/Down
                        case VK_UP:
                        case VK_DOWN:
                        case VK_NEXT:
                        case VK_PRIOR:
                            listBoxSamples->dispatchEvent(event);
                            event->cancel();
                            break;
                    }
                }
                else if (::getKeyModifier() == KeyModifierCTRL)
                {
                    switch (keyCode)
                    {
                        // Select pattern using Ctrl+Left/Right
                        case VK_LEFT:
                            eventKeyDownBinding_PreviousPattern();
                            event->cancel();
                            break;

                        case VK_RIGHT:
                            eventKeyDownBinding_NextPattern();
                            event->cancel();
                            break;
                    }
                }
                goto processOthers;
                break;

            case VK_TAB:
                if (screen->getModalControl())
                    break;

                getPatternEditorControl()->dispatchEvent(event);
                event->cancel();
                break;

            default:
processOthers:
                processShortcutsMilkyTracker(event);

                if (screen->getModalControl())
                    break;

                if (recorderLogic->getRecordMode())
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                }
                // if recordMode is false and focus is on pattern editor
                // we need to cancel the event in order to prevent it
                // from going into the pattern editor
                else if (screen->getFocusedControl() == static_cast<PPControl*>(getPatternEditorControl()))
                {
                    event->cancel();
                }
        }
    }
    else if (event->getID() == eKeyUp)
    {
        pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());
        //pp_uint16 scanCode = *(((pp_uint16*)event->getDataPtr())+1);

        switch (keyCode)
        {
            // Those are the keykombinations which are always routed to pattern editor control as long
            // as we're in Fasttracker editing mode
            case VK_ALT:
            case VK_SHIFT:
            case VK_CONTROL:
                if (screen->getModalControl())
                    break;

                getPatternEditorControl()->dispatchEvent(event);
                event->cancel();
                break;

            default:
                processShortcutsMilkyTracker(event);

                if (screen->getModalControl())
                    /*break;*/ return;

                if (recorderLogic->getRecordMode())
                {
                    getPatternEditorControl()->dispatchEvent(event);
                    event->cancel();
                }
                // if recordMode is false and focus is on pattern editor
                // we need to cancel the event in order to prevent it
                // from going into the pattern editor
                else if (screen->getFocusedControl() == static_cast<PPControl*>(getPatternEditorControl()))
                {
                    event->cancel();
                }
        }
    }
}


void Tracker::switchEditMode(EditModes mode)
{
    bool b = (mode == EditModeMilkyTracker);

    PPContainer* container = static_cast<PPContainer*>(screen->getControlByID(CONTAINER_MENU));
    ASSERT(container);

    // Assign keyboard bindings
    getPatternEditorControl()->setShowFocus(b);
    listBoxInstruments->setShowFocus(b);
    listBoxSamples->setShowFocus(b);
    listBoxOrderList->setShowFocus(b);
    sectionDiskMenu->setFileBrowserShowFocus(b);
    sectionDiskMenu->setCycleFilenames(b);
    container = static_cast<PPContainer*>(screen->getControlByID(CONTAINER_ABOUT));
    ASSERT(container);
    static_cast<PPListBox*>(container->getControlByID(LISTBOX_SONGTITLE))->setShowFocus(b);

    if (b)
    {
        eventKeyDownBindings = eventKeyDownBindingsMilkyTracker;
        screen->setFocus(listBoxInstruments, false);
    }
    else
    {
        eventKeyDownBindings = eventKeyDownBindingsFastTracker;
        recorderLogic->setRecordMode(true);
        eventKeyDownBinding_ToggleFT2Edit();
    }

    getPatternEditorControl()->switchEditMode(mode);

    editMode = mode;
}

// Process messagebox shortcuts (RETURN & ESC)
// the modal dialogs only appear to be modal, we're still getting
// keyboard events here in case a modal dialog box appears
// this is the handler which allows for esc + return handling in case
// of a modal dialog
static void simulateMouseClickEvent(PPControl* ctrl)
{
    PPPoint p = ctrl->getLocation();
    p.x += ctrl->getSize().width >> 1;
    p.y += ctrl->getSize().height >> 1;

    PPEvent e1(eLMouseDown, &p, sizeof(PPPoint));
    PPEvent e2(eLMouseUp, &p, sizeof(PPPoint));

    ctrl->dispatchEvent(&e1);
    ctrl->dispatchEvent(&e2);
}

bool Tracker::processMessageBoxShortcuts(PPEvent* event)
{
    PPControl* ctrl = screen->getModalControl();

    if (ctrl == NULL || !ctrl->isContainer() || event->getID() != eKeyDown)
        return false;

    PPSimpleVector<PPControl>& controls = static_cast<PPContainer*>(ctrl)->getControls();

    pp_int32 i;
    // if dialog contains list (list can also be an edit field btw.)
    // and something is being edited in that list we don't simulate
    // yes/no/cancel button presses
    for (i = 0; i < controls.size(); i++)
    {
        PPControl* ctrl = controls.get(i);
        if (ctrl->isListBox() && static_cast<PPListBox*>(ctrl)->isEditing())
            return true;
    }

    // iterate over controls in dialog and see whether we can find
    // yes/no/cancel buttons
    // if that's the case we simulate mouse button press
    pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());

    for (i = 0; i < controls.size(); i++)
    {
        PPControl* ctrl = controls.get(i);
        switch (ctrl->getID())
        {
            case PP_MESSAGEBOX_BUTTON_YES:
                if (keyCode == VK_RETURN)
                {
                    simulateMouseClickEvent(ctrl);
                    return true;
                }
                break;

            case PP_MESSAGEBOX_BUTTON_CANCEL:
            case PP_MESSAGEBOX_BUTTON_NO:
                if (keyCode == VK_ESCAPE)
                {
                    simulateMouseClickEvent(ctrl);
                    return true;
                }
                break;
        }
    }

    return false;
}